-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
--
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local utils = require 'mc.utils'

local ETH_OBJ_PATH<const> = '/bmc/kepler/Managers/1/EthernetInterfaces'
local ETH_OBJ_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces'
local ETH_PORT_INTERFACE<const> = 'bmc.kepler.Managers.EthernetInterfaces.MgmtPort'

local m = {}

function m.get_address_mode(DHCPv4UseDNSServers, DHCPv6UseDNSServers)
    if (not DHCPv4UseDNSServers) and (not DHCPv6UseDNSServers) then
        return 'Static'
    elseif DHCPv4UseDNSServers and (not DHCPv6UseDNSServers) then
        return 'IPv4'
    elseif DHCPv6UseDNSServers and (not DHCPv4UseDNSServers) then
        return 'IPv6'
    end

    return 'IPv4'
end

-- 资源树上的link地址和slaac地址是以fec0::01/64这样呈现的
-- 适配网页显示需要做转换
local function split_ipv6_address(ipv6_address)
    local strs = utils.split(ipv6_address, '/')
    return {addr = strs[1], prefix = strs[2]}
end

function m.get_ipv6_addresses(eth_obj, ipv6_obj)
    local ipv6_addresses = {}
    local pre_fix = ipv6_obj.PrefixLength == 0 and '' or ipv6_obj.PrefixLength
    ipv6_addresses[#ipv6_addresses + 1] = {
        IPAddress = ipv6_obj.IpAddr,
        IPAddressMode = ipv6_obj.IpMode,
        PrefixLength = pre_fix
    }
    local link_local = eth_obj.LinkLocalAddress
    if #link_local ~= 0 then
        local format_addr = split_ipv6_address(link_local)
        ipv6_addresses[#ipv6_addresses + 1] = {
            IPAddress = format_addr.addr,
            IPAddressMode = 'LinkLocal',
            PrefixLength = format_addr.prefix
        }
    end
    local slaac_list = eth_obj.SLAACAddressList
    for _, value in ipairs(slaac_list) do
        local format_slaac_addr = split_ipv6_address(value)
        ipv6_addresses[#ipv6_addresses + 1] = {
            IPAddress = format_slaac_addr.addr,
            IPAddressMode = 'SLAAC',
            PrefixLength = format_slaac_addr.prefix
        }
    end
    return ipv6_addresses
end

-- 用于web页面和资源树类型的相互转化
local type_switch_table = {
    mdb_to_web = {
        Lom = 'LOM',
        Dedicated = 'Dedicated',
        ExternalPCIe = 'ExternalPCIe',
        Ocp = 'OCP',
        Ocp2 = 'OCP2',
        Aggregation = 'Aggregation',
        FlexIO = 'FlexIO'
    },
    web_to_mdb = {
        LOM = 'Lom',
        Dedicated = 'Dedicated',
        ExternalPCIe = 'ExternalPCIe',
        OCP = 'Ocp',
        OCP2 = 'Ocp2',
        Aggregation = 'Aggregation',
        FlexIO = 'FlexIO'
    }
}

function m.get_network_port(net_mode, port_id, ports)
    local network_port = {}
    local management_network_port = {}
    local members = {
        LOM = {},
        Dedicated = {},
        ExternalPCIe = {},
        OCP = {},
        OCP2 = {},
        Aggregation = {},
        FlexIO = {}
    }

    for _, value in ipairs(ports) do
        local port_number = value.DevicePortId
        local member = {}
        if value.Type == 'Dedicated' then
            if value.Silkscreen == 'Mgmt' then
                member.PortName = 'Mgmt'
            else
                member.PortName = 'Port' .. value.DevicePortId .. '(' .. value.Silkscreen .. ')'
            end
        end
        if value.Id == port_id then
            management_network_port.PortNumber = port_number
            management_network_port.Type = value.Type
        end
        member.PortNumber = port_number
        member.LinkStatus = value.LinkStatus
        member.AdaptiveFlag = value.AdaptiveFlag
        local type = type_switch_table.mdb_to_web[value.Type]
        if type == nil then
            goto continue
        end
        members[type][#members[type] + 1] = member
        ::continue::
    end

    network_port = {
        Mode = net_mode,
        ManagementNetworkPort = management_network_port,
        Members = members
    }

    return network_port
end

function m.set_dedicated_vlan(enabled, vlan_id)
    if enabled == false and vlan_id == nil then
        return {enabled, 0, 1}
    end
    if enabled == true and vlan_id == nil then
        error(base_messages.PropertyMissing("VLANId"))
    end
    if enabled == nil and vlan_id ~= nil and vlan_id ~= 0 then
        return {true, vlan_id, 1}
    end
    if enabled == nil and vlan_id ~= nil and vlan_id == 0 then
        return {true, vlan_id, 1}
    end
    return {enabled, vlan_id, 1}
end

function m.check_ipv4_address_mode(ipv4_config)
    if ipv4_config.IPAddressMode and tostring(ipv4_config.IPAddressMode) == 'DHCP' and
        (ipv4_config.IPAddress or ipv4_config.SubnetMask or ipv4_config.Gateway) then
        error(custom_messages.IPv4InfoConflictwithDHCP())
        return false
    end

    return true
end

function m.check_ipv6_address_mode(ipv6_config)
    if ipv6_config.IPv6Addresses and ipv6_config.IPv6Addresses[1] and ipv6_config.IPv6Addresses[1].IPAddressMode then
        local ipv6_address = ipv6_config.IPv6Addresses[1]
        if tostring(ipv6_address.IPAddressMode) == 'DHCPv6' and
            (ipv6_address.IPAddress or ipv6_address.PrefixLength) then
            error(custom_messages.IPv6InfoConflictwithDHCPv6())
            return false
        end
    end

    return true
end

function m.check_ipv6_config(ipv6_config)
    if ipv6_config.Gateway or
        (ipv6_config.IPv6Addresses and ipv6_config.IPv6Addresses[1] and ipv6_config.IPv6Addresses[1].IPAddress) or
        (ipv6_config.IPv6Addresses and ipv6_config.IPv6Addresses[1] and ipv6_config.IPv6Addresses[1].PrefixLength) then
        return true
    end

    return false
end

local function get_portid_by_type(port_id, port)
    local port_obj = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local type = port.Type
    local device_port_id = port.PortNumber
    if type ~= nil then
        local type_valid = false
        for _, obj in pairs(port_obj) do
            if type_switch_table.web_to_mdb[type] == obj.Type then
                type_valid = true
                break
            end
        end
        if not type_valid then
            error(custom_messages.InvalidValue(type, "Type"))
        end
    end

    if not type or not device_port_id then
        return port_id
    end
    for _, obj in pairs(port_obj) do
        if device_port_id == obj.DevicePortId and type_switch_table.web_to_mdb[type] == obj.Type then
            port_id = obj.Id
            return port_id
        end
    end
    error(custom_messages.PortNotExist(type, device_port_id))
end

function m.set_network_config(network_port, ncsi_VLAN, eth_obj)
    local mode = eth_obj.NetMode
    local port_id = eth_obj.PortId
    local vlan_enable = eth_obj.VLANEnable
    local vlan_id = eth_obj.VLANId
    if network_port then
        if network_port.Mode then
            mode = tostring(network_port.Mode)
        end
        if network_port.ManagementNetworkPort then
            port_id = get_portid_by_type(port_id, network_port.ManagementNetworkPort)
        end
    end
    if ncsi_VLAN then
        if ncsi_VLAN.VLANId then
            vlan_id = ncsi_VLAN.VLANId
        end
        if ncsi_VLAN.Enabled ~= nil then
            vlan_enable = ncsi_VLAN.Enabled
        else
            vlan_enable = true
        end
        -- vlan关闭时，id设置为0
        if not vlan_enable then
            vlan_id = 0
        end
        if ncsi_VLAN.Enabled == true and ncsi_VLAN.VLANId == nil then
            error(base_messages.PropertyMissing("VLANId"))
        end
    end

    return {mode, port_id, vlan_enable, vlan_id}
end

function m.is_port_null(type, members)
    local port_obj = mdb.get_sub_objects(bus, ETH_OBJ_PATH, ETH_PORT_INTERFACE)
    local match = false
    for _, dedicated_port_info in ipairs(members) do
        local device_port_id = dedicated_port_info.PortNumber
        for _, obj in pairs(port_obj) do
            if device_port_id and device_port_id == obj.DevicePortId and tostring(type) == obj.Type then
                match = true
            end
        end

        if not match then
            error(custom_messages.PortNotExist(type, device_port_id))
        end

        match = false
    end

    return true
end

return m
